﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Media3D;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace BrowserClient
{
    /// <summary>
    /// Interaction logic for Page1.xaml
    /// </summary>
    public partial class Page1 : Page
    {
        double horizonRotation = 0;
        double verticalRotation = 0;

        double maxVerticalOffset = 0;
        double maxHorizonOffset = 0;

        double cameraFov = 45;

        bool initializeComplete = false;
        double  cameraHeight = 20;
        Point cameraPosition = new Point(-30, -30);
        Vector3D cameraLookDirection = new Vector3D(1, -1, 1);
        List<int> loadedBuildingIDs = new List<int>();
        Dictionary<MapService.Building, Model3D> loadedBuildingDictionary = new Dictionary<MapService.Building, Model3D>();

        Dictionary<string, BitmapImage> buildingTextures = new Dictionary<string, BitmapImage>();

        public Page1()
        {
            
            cameraLookDirection.Normalize();
            InitializeComponent();

            //Add a ground to scene
            WorldModel.Children.Add(StandardGroundGeometry.CreateGroundGeometry(Colors.Azure, 1000));

            //DynamicLoad();

            initializeComplete = true;
            UpdateCameraProperties();
        }
        /// <summary>
        /// Locate camera to a new position
        /// </summary>
        /// <param name="cameraPosition">Point in 2D,set Height to be 50 </param>
        public void LocateCamera(Point cameraPosition)
        {
            camera.Position = new Point3D(cameraPosition.X, 50, cameraPosition.Y);
            DynamicLoad();
        }
        AmbientLight ambientLight = new AmbientLight(Colors.White);
        DirectionalLight directionalLight = new DirectionalLight(Colors.White, new Vector3D(-1, -1, -1));
        /// <summary>
        /// Dynamic load buildings
        /// </summary>
        public void DynamicLoad()
        {
            MapService.ArrayOfInt intArray = new MapService.ArrayOfInt();
            foreach (int i in loadedBuildingIDs)
                intArray.Add(i);
            MapService.ServiceSoapClient client = new MapService.ServiceSoapClient();
            Rect viewField = CaculateCurrentViewField();
            MapService.Building[] buildings = client.GetMapDataOptimized((float)(viewField.Left + viewField.Width / 2), (float)(viewField.Top + viewField.Height / 2),
                                                                                                        (float)viewField.Width, (float)viewField.Height, intArray);
            LoadBuildings(buildings);
        }
        public void LoadBuildings(MapService.Building[] buildings)
        {
            if (buildings == null)
                return;
            foreach (MapService.Building b in buildings)
                loadedBuildingIDs.Add(b.ID);

            foreach (MapService.Building building in buildings)
            {
                GeometryModel3D model3D = StandardBuildingGeometry.CreateBuildingModel(building,buildingTextures);
                WorldModel.Children.Add(model3D);
                loadedBuildingDictionary.Add(building, model3D);
            }
            modelStatisticsLabel.Content = loadedBuildingIDs.Count.ToString() + " buildings in loaded";
        }
        /// <summary>
        /// Caculate field of view by current camera position
        /// </summary>
        /// <returns>Rectangle</returns>
        Rect CaculateCurrentViewField()
        {
            double cosToDown = -cameraLookDirection.Y;
            double angleToDown = Math.Acos(cosToDown);
            double angleSmall = angleToDown - Math.PI / 8;//Math.PI/8=22.5
            double angleLarge = angleToDown + Math.PI / 8;

            double extendNear = cameraHeight * Math.Tan(angleSmall);
            double extendFar = cameraHeight * Math.Tan(angleLarge);
            Vector directionInXoZ = new Vector(cameraLookDirection.X, cameraLookDirection.Z);
            directionInXoZ.Normalize();
            Vector cameraPositionInXoZ = new Vector(cameraPosition.X,cameraPosition.Y);
            Vector pNear = cameraPositionInXoZ + directionInXoZ * extendNear ;
            Vector pFar = cameraPositionInXoZ + directionInXoZ * extendFar;
            Vector lookAtDirectionPositive = new Vector(-directionInXoZ.Y, directionInXoZ.X);
            Vector lookAtDirectionNegtive = new Vector(directionInXoZ.X, -directionInXoZ.Y);
            Vector nearSidePointPositive = pNear + lookAtDirectionPositive * extendNear;
            Vector nearSidePointNegtive = pNear + lookAtDirectionNegtive * extendNear;
            Vector farSidePointPositive = pFar + lookAtDirectionPositive * extendFar;
            Vector farSidePointNegtive = pFar + lookAtDirectionNegtive * extendFar;
            Rect rect = new Rect();
            rect.X = Math.Min(Math.Min(Math.Min(nearSidePointNegtive.X, nearSidePointPositive.X), farSidePointNegtive.X), farSidePointPositive.X);
            rect.Y = Math.Min(Math.Min(Math.Min(nearSidePointNegtive.Y, nearSidePointPositive.Y), farSidePointNegtive.Y), farSidePointPositive.Y);
            double width = Math.Max(Math.Max(Math.Max(nearSidePointNegtive.X, nearSidePointPositive.X), farSidePointNegtive.X), farSidePointPositive.X);
            double height = Math.Max(Math.Max(Math.Max(nearSidePointNegtive.Y, nearSidePointPositive.Y), farSidePointNegtive.Y), farSidePointPositive.Y);
            
            rect.Width = width - rect.X;
            rect.Height = height - rect.Y;
            return rect;
        }
        
        bool drag = false;
        Point lastMousePos;
        private void Window_MouseDown(object sender, MouseButtonEventArgs e)
        {
            drag = true;
            lastMousePos = e.GetPosition(e.MouseDevice.Captured);
        }
        bool verticalDynamicLoad = false;
        bool horizonDynamicLoad = false;

        private void Window_MouseMove(object sender, MouseEventArgs e)
        {
            if (drag)
            {
                Point currentMousePos = e.GetPosition(e.MouseDevice.Captured);
                //flip Y axes
                Vector cameraMotion = new Vector(-currentMousePos.X + lastMousePos.X, currentMousePos.Y - lastMousePos.Y);
                RelocateCamera(cameraMotion);
                //update last mouse position
                lastMousePos = currentMousePos;

                if (Math.Abs(lastMousePos.X - maxHorizonOffset) > 40)
                {
                    maxHorizonOffset = lastMousePos.X;
                    horizonDynamicLoad = true;
                }
                if (Math.Abs(lastMousePos.Y - maxVerticalOffset) > 40)
                {
                    maxVerticalOffset = lastMousePos.Y;
                    verticalDynamicLoad = true;
                }

            }
        }
        /// <summary>
        /// Relocate camera position
        /// </summary>
        /// <param name="cameraMotion2D">camera motion in screen space captured from mouse's motion</param>
        public void RelocateCamera(Vector cameraMotion2D)
        {
            Vector3D forwardVector = new Vector3D(camera.LookDirection.X, 0, camera.LookDirection.Z);
            forwardVector.Normalize();
            Vector3D right = Vector3D.CrossProduct(camera.LookDirection,camera.UpDirection);
            float slowdownFactor = 0.2f;
            camera.Position = camera.Position + forwardVector * cameraMotion2D.Y * slowdownFactor + right * cameraMotion2D.X * slowdownFactor;
            cameraPosition.X = camera.Position.X;
            cameraPosition.Y = camera.Position.Z;
        }
        private void Window_MouseUp(object sender, MouseButtonEventArgs e)
        {
            drag = false;
            if (verticalDynamicLoad || horizonDynamicLoad)
            {
                DynamicLoad();
                verticalDynamicLoad = false;
                horizonDynamicLoad = false;
            }
        }
        /// <summary>
        /// Update camera properties
        /// </summary>
        void UpdateCameraProperties()
        {
            double proj = Math.Cos(verticalRotation);
            double y = Math.Sin(verticalRotation);
            double x = Math.Cos(horizonRotation) * proj;
            double z = Math.Sin(horizonRotation) * proj;
            camera.LookDirection = new Vector3D(x, y, z);
            cameraLookDirection = camera.LookDirection;
            camera.Position = new Point3D(camera.Position.X, cameraHeight, camera.Position.Z);
            string tip = "Position=" + camera.Position.ToString() + " LookDirection=" + camera.LookDirection.ToString();
            tip += "\n Horizon slider value=" + rotateHorizonSlider.Value.ToString() + " Vertical slider value=" + rotateVerticalSlider.Value.ToString();
            lbCameraProperties.Content = tip;
        }

        
        private void rotateHorizonSlider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
        {
            if (initializeComplete)
            {
                double rotateIntensity = (rotateHorizonSlider.Value*2 - rotateHorizonSlider.Maximum) / rotateHorizonSlider.Maximum;
                horizonRotation = rotateIntensity * Math.PI;
                UpdateCameraProperties();
                if (Math.Abs(rotateHorizonSlider.Value - horizonRotation) > Math.PI / 10)
                {
                    //horizonRotation = rotateHorizonSlider.Value;
                    DynamicLoad();
                }
            }
        }

        private void rotateVerticalSlider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
        {
            if (initializeComplete)
            {
                double rotateIntensity = (rotateVerticalSlider.Value*2 - rotateVerticalSlider.Maximum) / rotateVerticalSlider.Maximum;
                verticalRotation = rotateIntensity * Math.PI;
                UpdateCameraProperties();
                if (Math.Abs(rotateVerticalSlider.Value - verticalRotation) > Math.PI / 10)
                {
                    //verticalRotation = rotateVerticalSlider.Value;
                    DynamicLoad();
                }
            }
        }

        private void heightSlider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
        {
            if (initializeComplete)
            {
                cameraHeight = heightSlider.Value;
                UpdateCameraProperties();
            }
        }
        
        private void Page_MouseWheel(object sender, MouseWheelEventArgs e)
        {
            cameraFov += e.Delta / 400.0f;
            camera.FieldOfView = cameraFov;
        }

    }
      #region Utilities

    /// <summary>
    /// Standard geometry- the ground
    /// </summary>
    public class StandardGroundGeometry
    {
        /// <summary>
        /// Ground vertices
        /// </summary>
        public static Point3D[] GroundVertices =
        {
            new Point3D(-0.5,0,0.5),
            new Point3D(0.5,0,0.5),
            new Point3D(-0.5,0,-0.5),

            new Point3D(0.5,0,0.5),
            new Point3D(0.5,0,-0.5),
            new Point3D(-0.5,0,-0.5),
        };
        /// <summary>
        /// Create typical ground geometry
        /// </summary>
        /// <param name="groundColor">color of ground</param>
        /// <param name="size">size of ground</param>
        /// <returns>GeometryModel3D</returns>
        public static GeometryModel3D CreateGroundGeometry(Color groundColor, double size)
        {
            MeshGeometry3D mesh = CreateGroundModel();
            ScaleTransform3D scale = new ScaleTransform3D(size, 1, size);
            DiffuseMaterial material = new DiffuseMaterial(new SolidColorBrush(groundColor));

            GeometryModel3D geometry = new GeometryModel3D(mesh, material);
            geometry.Transform = scale;
            return geometry;
        }
        /// <summary>
        /// Create model for typical ground mesh
        /// </summary>
        /// <returns>MeshGeometry3D</returns>
        public static MeshGeometry3D CreateGroundModel()
        {
            MeshGeometry3D mesh = new MeshGeometry3D();
            foreach (Point3D vertex in GroundVertices)
            {
                mesh.Positions.Add(vertex);
                mesh.Normals.Add(new Vector3D(0, 1, 0));
            }
            return mesh;
        }
    }
    /// <summary>
    /// Standard geometry - building
    /// </summary>
    public class StandardBuildingGeometry
    {
        /// <summary>
        /// Create model for a building
        /// </summary>
        /// <param name="building">Building infomation</param>
        /// <returns>GeometryModel3D</returns>
        public static GeometryModel3D CreateBuildingModel(MapService.Building building,Dictionary<string,BitmapImage> existTextures)
        {
            MeshGeometry3D mesh = CreateBuildingMesh(building);
            DiffuseMaterial diffuseMaterial = new DiffuseMaterial();
            diffuseMaterial.AmbientColor = Colors.White;
            string serviceURI = "http://localhost:1429/VirtualMapService/Resources/";
            string texturePath = building.BuildingTexturePath;
            string combineResource = serviceURI + texturePath;
            if (!existTextures.ContainsKey(combineResource))
            {
                existTextures.Add(combineResource, new BitmapImage(new Uri(combineResource)));
            }
            diffuseMaterial.Brush = new ImageBrush(existTextures[combineResource]);
            diffuseMaterial.Color = Colors.White;//building.DiffuseColor;

            GeometryModel3D geometry = new GeometryModel3D(mesh, diffuseMaterial);

            Transform3DGroup transform = new Transform3DGroup();
            TranslateTransform3D translation = new TranslateTransform3D(building.Position.X, 0, building.Position.Y);
            ScaleTransform3D scale = new ScaleTransform3D(building.Width, building.Height, building.Length);
            transform.Children.Add(scale);
            transform.Children.Add(translation);
            
            geometry.Transform = transform;
            return geometry;
        }
        /// <summary>
        /// Create standard mesh for a building
        /// </summary>
        /// <param name="building">building infomation</param>
        /// <returns>MeshGeometry3D</returns>
        public static MeshGeometry3D CreateBuildingMesh(MapService.Building building)
        {
            MeshGeometry3D mesh = new MeshGeometry3D();
            for (int i = 0; i < TriangleIndices.Length; i++)
            {
                mesh.TriangleIndices.Add(i);
                mesh.Positions.Add(BuildingVertices[TriangleIndices[i]]);
            }
            for (int face = 0; face < 5; face++)
            {
                for (int triangle = 0; triangle < 6; triangle++)
                {
                    mesh.Normals.Add(BuildingNormals[face]);
                    if (face != 4)
                        mesh.TextureCoordinates.Add(TexCoords[triangle]);
                }
            }
            return mesh;
        }
        /// <summary>
        /// Six points for a standard building
        /// </summary>
        public static Point3D[] BuildingVertices =
        {
            //bottom
            new Point3D(-0.5,0,-0.5),
            new Point3D(0.5,0,-0.5),
            new Point3D(0.5,0,0.5),
            new Point3D(-0.5,0,0.5),
            //top
            new Point3D(-0.5,1,-0.5),
            new Point3D(0.5,1,-0.5),
            new Point3D(0.5,1,0.5),
            new Point3D(-0.5,1,0.5),
            
        };
        public static Vector3D[] BuildingNormals =
        {
            new Vector3D(0,0,-1),
            new Vector3D(1,0,0),
            new Vector3D(-1,0,0),
            new Vector3D(-1,0,0),
            new Vector3D(0,1,0),
        };
        /// <summary>
        /// Triangle indices
        /// </summary>
        public static int[] TriangleIndices ={0,4,5,
                                                          0,5,1,
                                                          1,5,6,
                                                          1,6,2,
                                                          2,6,7,
                                                          2,7,3,
                                                          3,7,4,
                                                          3,4,0,
                                                          7,5,4,
                                                          7,6,5,
                                                          };
        /// <summary>
        /// Texture coordinates
        /// </summary>
        public static Point[] TexCoords=
        {
            new Point(1,1),new Point(1,0),new Point(0,0),new Point(1,1),new Point(0,0),new Point(0,1),
        };
    }
    [Serializable]
    public class Building
    {
        public Point Position;
        public int Width;
        public int Length;
        public int Height;
        public Color DiffuseColor;
    }

    #endregion
}
